/*
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 *    CostBenefitAnalysis.java
 *    Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
 *
 */

package weka.gui.beans;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GraphicsEnvironment;
import java.awt.GridLayout;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.beans.EventSetDescriptor;
import java.beans.PropertyVetoException;
import java.beans.VetoableChangeListener;
import java.beans.beancontext.BeanContext;
import java.beans.beancontext.BeanContextChild;
import java.beans.beancontext.BeanContextChildSupport;
import java.io.Serializable;
import java.util.Enumeration;
import java.util.Vector;

import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JSlider;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import weka.classifiers.evaluation.ThresholdCurve;
import weka.core.Attribute;
import weka.core.FastVector;
import weka.core.Instance;
import weka.core.Instances;
import weka.core.Utils;
import weka.gui.Logger;
import weka.gui.visualize.VisualizePanel;
import weka.gui.visualize.Plot2D;
import weka.gui.visualize.PlotData2D;

/**
 * Bean that aids in analyzing cost/benefit tradeoffs.
 * 
 * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
 * @version $Revision: 7647 $
 */
public class CostBenefitAnalysis extends JPanel implements BeanCommon,
		ThresholdDataListener, Visible, UserRequestAcceptor, Serializable,
		BeanContextChild {

	/** For serialization */
	private static final long serialVersionUID = 8647471654613320469L;

	protected BeanVisual m_visual;

	protected transient JFrame m_popupFrame;

	protected boolean m_framePoppedUp = false;

	private transient AnalysisPanel m_analysisPanel;

	/**
	 * True if this bean's appearance is the design mode appearance
	 */
	protected boolean m_design;

	/**
	 * BeanContex that this bean might be contained within
	 */
	protected transient BeanContext m_beanContext = null;

	/**
	 * BeanContextChild support
	 */
	protected BeanContextChildSupport m_bcSupport = new BeanContextChildSupport(
			this);

	/**
	 * The object sending us data (we allow only one connection at any one time)
	 */
	protected Object m_listenee;

	/**
	 * Inner class for displaying the plots and all control widgets.
	 * 
	 * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
	 */
	protected static class AnalysisPanel extends JPanel {

		/** For serialization */
		private static final long serialVersionUID = 5364871945448769003L;

		/** Displays the performance graphs(s) */
		protected VisualizePanel m_performancePanel = new VisualizePanel();

		/** Displays the cost/benefit (profit/loss) graph */
		protected VisualizePanel m_costBenefitPanel = new VisualizePanel();

		/**
		 * The class attribute from the data that was used to generate the
		 * threshold curve
		 */
		protected Attribute m_classAttribute;

		/** Data for the threshold curve */
		protected PlotData2D m_masterPlot;

		/** Data for the cost/benefit curve */
		protected PlotData2D m_costBenefit;

		/** The size of the points being plotted */
		protected int[] m_shapeSizes;

		/** The index of the previous plotted point that was highlighted */
		protected int m_previousShapeIndex = -1;

		/** The slider for adjusting the threshold */
		protected JSlider m_thresholdSlider = new JSlider(0, 100, 0);

		protected JRadioButton m_percPop = new JRadioButton(Messages
				.getInstance().getString(
						"CostBenefitAnalysis_PpercPop_JRadioButton_Text"));
		protected JRadioButton m_percOfTarget = new JRadioButton(Messages
				.getInstance().getString(
						"CostBenefitAnalysis_PercOfTarget_JRadioButton_Text"));
		protected JRadioButton m_threshold = new JRadioButton(Messages
				.getInstance().getString(
						"CostBenefitAnalysis_Threshold_JRadioButton_Text"));

		protected JLabel m_percPopLab = new JLabel();
		protected JLabel m_percOfTargetLab = new JLabel();
		protected JLabel m_thresholdLab = new JLabel();

		// Confusion matrix stuff
		protected JLabel m_conf_predictedA = new JLabel(Messages.getInstance()
				.getString("CostBenefitAnalysis_Conf_PredictedA_JLabel_Text"),
				SwingConstants.RIGHT);
		protected JLabel m_conf_predictedB = new JLabel(Messages.getInstance()
				.getString("CostBenefitAnalysis_Conf_PredictedB_JLabel_Text"),
				SwingConstants.RIGHT);
		protected JLabel m_conf_actualA = new JLabel(Messages.getInstance()
				.getString("CostBenefitAnalysis_Conf_ActualA_JLabel_Text"));
		protected JLabel m_conf_actualB = new JLabel(Messages.getInstance()
				.getString("CostBenefitAnalysis_Conf_ActualB_JLabel_Text"));
		protected ConfusionCell m_conf_aa = new ConfusionCell();
		protected ConfusionCell m_conf_ab = new ConfusionCell();
		protected ConfusionCell m_conf_ba = new ConfusionCell();
		protected ConfusionCell m_conf_bb = new ConfusionCell();

		// Cost matrix stuff
		protected JLabel m_cost_predictedA = new JLabel(Messages.getInstance()
				.getString("CostBenefitAnalysis_Cost_PredictedA_JLabel_Text"),
				SwingConstants.RIGHT);
		protected JLabel m_cost_predictedB = new JLabel(Messages.getInstance()
				.getString("CostBenefitAnalysis_Cost_PredictedB_JLabel_Text"),
				SwingConstants.RIGHT);
		protected JLabel m_cost_actualA = new JLabel(Messages.getInstance()
				.getString("CostBenefitAnalysis_Cost_ActualA_JLabel_Text"));
		protected JLabel m_cost_actualB = new JLabel(Messages.getInstance()
				.getString("CostBenefitAnalysis_Cost_ActualB_JLabel_Text"));
		protected JTextField m_cost_aa = new JTextField("0.0", 5);
		protected JTextField m_cost_ab = new JTextField("1.0", 5);
		protected JTextField m_cost_ba = new JTextField("1.0", 5);
		protected JTextField m_cost_bb = new JTextField("0.0", 5);
		protected JButton m_maximizeCB = new JButton(Messages.getInstance()
				.getString("CostBenefitAnalysis_MaximizeCB_JButton_Text"));
		protected JButton m_minimizeCB = new JButton(Messages.getInstance()
				.getString("CostBenefitAnalysis_MinimizeCB_JButton_Text"));
		protected JRadioButton m_costR = new JRadioButton(Messages
				.getInstance().getString(
						"CostBenefitAnalysis_CostR_JRadioButton_Text"));
		protected JRadioButton m_benefitR = new JRadioButton(Messages
				.getInstance().getString(
						"CostBenefitAnalysis_BenefitR_JRadioButton_Text"));
		protected JLabel m_costBenefitL = new JLabel(Messages.getInstance()
				.getString("CostBenefitAnalysis_CostBenefitL_JLabel_Text"),
				SwingConstants.RIGHT);
		protected JLabel m_costBenefitV = new JLabel("0");
		protected JLabel m_randomV = new JLabel("0");
		protected JLabel m_gainV = new JLabel("0");

		protected int m_originalPopSize;

		/** Population text field */
		protected JTextField m_totalPopField = new JTextField(6);
		protected int m_totalPopPrevious;

		/** Classification accuracy */
		protected JLabel m_classificationAccV = new JLabel("-");

		// Only update curve & stats if values in cost matrix have changed
		protected double m_tpPrevious;
		protected double m_fpPrevious;
		protected double m_tnPrevious;
		protected double m_fnPrevious;

		/**
		 * Inner class for handling a single cell in the confusion matrix.
		 * Displays the value, value as a percentage of total population and
		 * graphical depiction of percentage.
		 * 
		 * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
		 */
		protected static class ConfusionCell extends JPanel {

			/** For serialization */
			private static final long serialVersionUID = 6148640235434494767L;

			private JLabel m_conf_cell = new JLabel("-", SwingConstants.RIGHT);
			JLabel m_conf_perc = new JLabel("-", SwingConstants.RIGHT);

			private JPanel m_percentageP;

			protected double m_percentage = 0;

			public ConfusionCell() {
				setLayout(new BorderLayout());
				setBorder(BorderFactory.createEtchedBorder());

				add(m_conf_cell, BorderLayout.NORTH);

				m_percentageP = new JPanel() {
					public void paintComponent(Graphics gx) {
						super.paintComponent(gx);

						if (m_percentage > 0) {
							gx.setColor(Color.BLUE);
							int height = this.getHeight();
							double width = this.getWidth();
							int barWidth = (int) (m_percentage * width);
							gx.fillRect(0, 0, barWidth, height);
						}
					}
				};

				Dimension d = new Dimension(30, 5);
				m_percentageP.setMinimumSize(d);
				m_percentageP.setPreferredSize(d);
				JPanel percHolder = new JPanel();
				percHolder.setLayout(new BorderLayout());
				percHolder.add(m_percentageP, BorderLayout.CENTER);
				percHolder.add(m_conf_perc, BorderLayout.EAST);

				add(percHolder, BorderLayout.SOUTH);
			}

			/**
			 * Set the value of a cell.
			 * 
			 * @param cellValue
			 *            the value of the cell
			 * @param max
			 *            the max (for setting value as a percentage)
			 * @param scaleFactor
			 *            scale the value by this amount
			 * @param precision
			 *            precision for the percentage value
			 */
			public void setCellValue(double cellValue, double max,
					double scaleFactor, int precision) {
				if (!Instance.isMissingValue(cellValue)) {
					m_percentage = cellValue / max;
				} else {
					m_percentage = 0;
				}

				m_conf_cell.setText(Utils.doubleToString(
						(cellValue * scaleFactor), 0));
				m_conf_perc.setText(Utils.doubleToString(m_percentage * 100.0,
						precision) + "%");

				// refresh the percentage bar
				m_percentageP.repaint();
			}
		}

		public AnalysisPanel() {
			setLayout(new BorderLayout());
			m_performancePanel.setShowAttBars(false);
			m_performancePanel.setShowClassPanel(false);
			m_costBenefitPanel.setShowAttBars(false);
			m_costBenefitPanel.setShowClassPanel(false);

			Dimension size = new Dimension(500, 400);
			m_performancePanel.setPreferredSize(size);
			m_performancePanel.setMinimumSize(size);

			size = new Dimension(500, 400);
			m_costBenefitPanel.setMinimumSize(size);
			m_costBenefitPanel.setPreferredSize(size);

			m_thresholdSlider.addChangeListener(new ChangeListener() {
				public void stateChanged(ChangeEvent e) {
					updateInfoForSliderValue((double) m_thresholdSlider
							.getValue() / 100.0);
				}
			});

			JPanel plotHolder = new JPanel();
			plotHolder.setLayout(new GridLayout(1, 2));
			plotHolder.add(m_performancePanel);
			plotHolder.add(m_costBenefitPanel);
			add(plotHolder, BorderLayout.CENTER);

			JPanel lowerPanel = new JPanel();
			lowerPanel.setLayout(new BorderLayout());

			ButtonGroup bGroup = new ButtonGroup();
			bGroup.add(m_percPop);
			bGroup.add(m_percOfTarget);
			bGroup.add(m_threshold);

			ButtonGroup bGroup2 = new ButtonGroup();
			bGroup2.add(m_costR);
			bGroup2.add(m_benefitR);
			ActionListener rl = new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					if (m_costR.isSelected()) {
						m_costBenefitL
								.setText(Messages
										.getInstance()
										.getString(
												"CostBenefitAnalysis_AnalysisPanel_CostBenefitL_SetText_Text_First"));
					} else {
						m_costBenefitL
								.setText(Messages
										.getInstance()
										.getString(
												"CostBenefitAnalysis_AnalysisPanel_CostBenefitL_SetText_Text_Second"));
					}

					double gain = Double.parseDouble(m_gainV.getText());
					gain = -gain;
					m_gainV.setText(Utils.doubleToString(gain, 2));
				}
			};
			m_costR.addActionListener(rl);
			m_benefitR.addActionListener(rl);
			m_costR.setSelected(true);

			m_percPop.setSelected(true);
			JPanel threshPanel = new JPanel();
			threshPanel.setLayout(new BorderLayout());
			JPanel radioHolder = new JPanel();
			radioHolder.setLayout(new FlowLayout());
			radioHolder.add(m_percPop);
			radioHolder.add(m_percOfTarget);
			radioHolder.add(m_threshold);
			threshPanel.add(radioHolder, BorderLayout.NORTH);
			threshPanel.add(m_thresholdSlider, BorderLayout.SOUTH);

			JPanel threshInfoPanel = new JPanel();
			threshInfoPanel.setLayout(new GridLayout(3, 2));
			threshInfoPanel
					.add(new JLabel(
							Messages.getInstance()
									.getString(
											"CostBenefitAnalysis_AnalysisPanel_ThreshInfoPanel_JLabel_Text_First"),
							SwingConstants.RIGHT));
			threshInfoPanel.add(m_percPopLab);
			threshInfoPanel
					.add(new JLabel(
							Messages.getInstance()
									.getString(
											"CostBenefitAnalysis_AnalysisPanel_ThreshInfoPanel_JLabel_Text_Second"),
							SwingConstants.RIGHT));
			threshInfoPanel.add(m_percOfTargetLab);
			threshInfoPanel
					.add(new JLabel(
							Messages.getInstance()
									.getString(
											"CostBenefitAnalysis_AnalysisPanel_ThreshInfoPanel_JLabel_Text_Third"),
							SwingConstants.RIGHT));
			threshInfoPanel.add(m_thresholdLab);

			JPanel threshHolder = new JPanel();
			threshHolder
					.setBorder(BorderFactory
							.createTitledBorder(Messages
									.getInstance()
									.getString(
											"CostBenefitAnalysis_AnalysisPanel_ThreshInfoPanel_ThreshHolder_SetBorder_BorderFactory_CreateTitledBorder_Text")));
			threshHolder.setLayout(new BorderLayout());
			threshHolder.add(threshPanel, BorderLayout.CENTER);
			threshHolder.add(threshInfoPanel, BorderLayout.EAST);

			lowerPanel.add(threshHolder, BorderLayout.NORTH);

			// holder for the two matrixes
			JPanel matrixHolder = new JPanel();
			matrixHolder.setLayout(new GridLayout(1, 2));

			// confusion matrix
			JPanel confusionPanel = new JPanel();
			confusionPanel.setLayout(new GridLayout(3, 3));
			confusionPanel.add(m_conf_predictedA);
			confusionPanel.add(m_conf_predictedB);
			confusionPanel.add(new JLabel()); // dummy
			confusionPanel.add(m_conf_aa);
			confusionPanel.add(m_conf_ab);
			confusionPanel.add(m_conf_actualA);
			confusionPanel.add(m_conf_ba);
			confusionPanel.add(m_conf_bb);
			confusionPanel.add(m_conf_actualB);
			JPanel tempHolderCA = new JPanel();
			tempHolderCA.setLayout(new BorderLayout());
			tempHolderCA
					.setBorder(BorderFactory
							.createTitledBorder(Messages
									.getInstance()
									.getString(
											"CostBenefitAnalysis_AnalysisPanel_ThreshInfoPanel_TempHolderCA_SetBorder_BorderFactory_CreateTitledBorder_Text")));
			tempHolderCA.add(confusionPanel, BorderLayout.CENTER);

			JPanel accHolder = new JPanel();
			accHolder.setLayout(new FlowLayout(FlowLayout.LEFT));
			accHolder
					.add(new JLabel(
							Messages.getInstance()
									.getString(
											"CostBenefitAnalysis_AnalysisPanel_ThreshInfoPanel_AccHolder_Add_JLabel_Text")));
			accHolder.add(m_classificationAccV);
			tempHolderCA.add(accHolder, BorderLayout.SOUTH);

			matrixHolder.add(tempHolderCA);

			// cost matrix
			JPanel costPanel = new JPanel();
			costPanel
					.setBorder(BorderFactory
							.createTitledBorder(Messages
									.getInstance()
									.getString(
											"CostBenefitAnalysis_AnalysisPanel_CostPanel_SetBorder_BorderFactory_CcreateTitledBorder_Text")));
			costPanel.setLayout(new BorderLayout());

			JPanel cmHolder = new JPanel();
			cmHolder.setLayout(new GridLayout(3, 3));
			cmHolder.add(m_cost_predictedA);
			cmHolder.add(m_cost_predictedB);
			cmHolder.add(new JLabel()); // dummy
			cmHolder.add(m_cost_aa);
			cmHolder.add(m_cost_ab);
			cmHolder.add(m_cost_actualA);
			cmHolder.add(m_cost_ba);
			cmHolder.add(m_cost_bb);
			cmHolder.add(m_cost_actualB);
			costPanel.add(cmHolder, BorderLayout.CENTER);

			FocusListener fl = new FocusListener() {
				public void focusGained(FocusEvent e) {

				}

				public void focusLost(FocusEvent e) {
					if (constructCostBenefitData()) {
						try {
							m_costBenefitPanel.setMasterPlot(m_costBenefit);
							m_costBenefitPanel.validate();
							m_costBenefitPanel.repaint();
						} catch (Exception ex) {
							ex.printStackTrace();
						}
						updateCostBenefit();
					}
				}
			};

			ActionListener al = new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					if (constructCostBenefitData()) {
						try {
							m_costBenefitPanel.setMasterPlot(m_costBenefit);
							m_costBenefitPanel.validate();
							m_costBenefitPanel.repaint();
						} catch (Exception ex) {
							ex.printStackTrace();
						}
						updateCostBenefit();
					}
				}
			};

			m_cost_aa.addFocusListener(fl);
			m_cost_aa.addActionListener(al);
			m_cost_ab.addFocusListener(fl);
			m_cost_ab.addActionListener(al);
			m_cost_ba.addFocusListener(fl);
			m_cost_ba.addActionListener(al);
			m_cost_bb.addFocusListener(fl);
			m_cost_bb.addActionListener(al);

			m_totalPopField.addFocusListener(fl);
			m_totalPopField.addActionListener(al);

			JPanel cbHolder = new JPanel();
			cbHolder.setLayout(new BorderLayout());
			JPanel tempP = new JPanel();
			tempP.setLayout(new GridLayout(3, 2));
			tempP.add(m_costBenefitL);
			tempP.add(m_costBenefitV);
			tempP.add(new JLabel(
					Messages.getInstance()
							.getString(
									"CostBenefitAnalysis_AnalysisPanel_CbHolder_TempP_JPanel_Add_JLabel_Text_First"),
					SwingConstants.RIGHT));
			tempP.add(m_randomV);
			tempP.add(new JLabel(
					Messages.getInstance()
							.getString(
									"CostBenefitAnalysis_AnalysisPanel_CbHolder_TempP_JPanel_Add_JLabel_Text_Second"),
					SwingConstants.RIGHT));
			tempP.add(m_gainV);
			cbHolder.add(tempP, BorderLayout.NORTH);
			JPanel butHolder = new JPanel();
			butHolder.setLayout(new GridLayout(2, 1));
			butHolder.add(m_maximizeCB);
			butHolder.add(m_minimizeCB);
			m_maximizeCB.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					findMaxMinCB(true);
				}
			});

			m_minimizeCB.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					findMaxMinCB(false);
				}
			});

			cbHolder.add(butHolder, BorderLayout.SOUTH);
			costPanel.add(cbHolder, BorderLayout.EAST);

			JPanel popCBR = new JPanel();
			popCBR.setLayout(new GridLayout(1, 2));
			JPanel popHolder = new JPanel();
			popHolder.setLayout(new FlowLayout(FlowLayout.LEFT));
			popHolder
					.add(new JLabel(
							Messages.getInstance()
									.getString(
											"CostBenefitAnalysis_AnalysisPanel_PopHolder_Add_JLabel_Text")));
			popHolder.add(m_totalPopField);

			JPanel radioHolder2 = new JPanel();
			radioHolder2.setLayout(new FlowLayout(FlowLayout.RIGHT));
			radioHolder2.add(m_costR);
			radioHolder2.add(m_benefitR);
			popCBR.add(popHolder);
			popCBR.add(radioHolder2);

			costPanel.add(popCBR, BorderLayout.SOUTH);

			matrixHolder.add(costPanel);

			lowerPanel.add(matrixHolder, BorderLayout.SOUTH);

			// popAccHolder.add(popHolder);

			// popAccHolder.add(accHolder);

			/*
			 * JPanel lowerPanel2 = new JPanel(); lowerPanel2.setLayout(new
			 * BorderLayout()); lowerPanel2.add(lowerPanel, BorderLayout.NORTH);
			 * lowerPanel2.add(popAccHolder, BorderLayout.SOUTH);
			 */

			add(lowerPanel, BorderLayout.SOUTH);

		}

		private void findMaxMinCB(boolean max) {
			double maxMin = (max) ? Double.NEGATIVE_INFINITY
					: Double.POSITIVE_INFINITY;

			Instances cBCurve = m_costBenefit.getPlotInstances();
			int maxMinIndex = 0;

			for (int i = 0; i < cBCurve.numInstances(); i++) {
				Instance current = cBCurve.instance(i);
				if (max) {
					if (current.value(1) > maxMin) {
						maxMin = current.value(1);
						maxMinIndex = i;
					}
				} else {
					if (current.value(1) < maxMin) {
						maxMin = current.value(1);
						maxMinIndex = i;
					}
				}
			}

			// set the slider to the correct position
			int indexOfSampleSize = m_masterPlot.getPlotInstances()
					.attribute(ThresholdCurve.SAMPLE_SIZE_NAME).index();
			int indexOfPercOfTarget = m_masterPlot.getPlotInstances()
					.attribute(ThresholdCurve.RECALL_NAME).index();
			int indexOfThreshold = m_masterPlot.getPlotInstances()
					.attribute(ThresholdCurve.THRESHOLD_NAME).index();
			int indexOfMetric;

			if (m_percPop.isSelected()) {
				indexOfMetric = indexOfSampleSize;
			} else if (m_percOfTarget.isSelected()) {
				indexOfMetric = indexOfPercOfTarget;
			} else {
				indexOfMetric = indexOfThreshold;
			}

			double valueOfMetric = m_masterPlot.getPlotInstances()
					.instance(maxMinIndex).value(indexOfMetric);
			valueOfMetric *= 100.0;

			// set the approximate location of the slider
			m_thresholdSlider.setValue((int) valueOfMetric);

			// make sure the actual values relate to the true min/max rather
			// than being off due to slider location error.
			updateInfoGivenIndex(maxMinIndex);
		}

		private void updateCostBenefit() {
			double value = (double) m_thresholdSlider.getValue() / 100.0;
			Instances plotInstances = m_masterPlot.getPlotInstances();
			int indexOfSampleSize = m_masterPlot.getPlotInstances()
					.attribute(ThresholdCurve.SAMPLE_SIZE_NAME).index();
			int indexOfPercOfTarget = m_masterPlot.getPlotInstances()
					.attribute(ThresholdCurve.RECALL_NAME).index();
			int indexOfThreshold = m_masterPlot.getPlotInstances()
					.attribute(ThresholdCurve.THRESHOLD_NAME).index();
			int indexOfMetric;

			if (m_percPop.isSelected()) {
				indexOfMetric = indexOfSampleSize;
			} else if (m_percOfTarget.isSelected()) {
				indexOfMetric = indexOfPercOfTarget;
			} else {
				indexOfMetric = indexOfThreshold;
			}

			int index = findIndexForValue(value, plotInstances, indexOfMetric);
			updateCBRandomGainInfo(index);
		}

		private void updateCBRandomGainInfo(int index) {
			double requestedPopSize = m_originalPopSize;
			try {
				requestedPopSize = Double
						.parseDouble(m_totalPopField.getText());
			} catch (NumberFormatException e) {
			}
			double scaleFactor = requestedPopSize / m_originalPopSize;

			double CB = m_costBenefit.getPlotInstances().instance(index)
					.value(1);
			m_costBenefitV.setText(Utils.doubleToString(CB, 2));

			double totalRandomCB = 0.0;
			Instance first = m_masterPlot.getPlotInstances().instance(0);
			double totalPos = first.value(m_masterPlot.getPlotInstances()
					.attribute(ThresholdCurve.TRUE_POS_NAME).index())
					* scaleFactor;
			double totalNeg = first.value(m_masterPlot.getPlotInstances()
					.attribute(ThresholdCurve.FALSE_POS_NAME)) * scaleFactor;

			double posInSample = (totalPos * (Double.parseDouble(m_percPopLab
					.getText()) / 100.0));
			double negInSample = (totalNeg * (Double.parseDouble(m_percPopLab
					.getText()) / 100.0));
			double posOutSample = totalPos - posInSample;
			double negOutSample = totalNeg - negInSample;

			double tpCost = 0.0;
			try {
				tpCost = Double.parseDouble(m_cost_aa.getText());
			} catch (NumberFormatException n) {
			}
			double fpCost = 0.0;
			try {
				fpCost = Double.parseDouble(m_cost_ba.getText());
			} catch (NumberFormatException n) {
			}
			double tnCost = 0.0;
			try {
				tnCost = Double.parseDouble(m_cost_bb.getText());
			} catch (NumberFormatException n) {
			}
			double fnCost = 0.0;
			try {
				fnCost = Double.parseDouble(m_cost_ab.getText());
			} catch (NumberFormatException n) {
			}

			totalRandomCB += posInSample * tpCost;
			totalRandomCB += negInSample * fpCost;
			totalRandomCB += posOutSample * fnCost;
			totalRandomCB += negOutSample * tnCost;

			m_randomV.setText(Utils.doubleToString(totalRandomCB, 2));
			double gain = (m_costR.isSelected()) ? totalRandomCB - CB : CB
					- totalRandomCB;
			m_gainV.setText(Utils.doubleToString(gain, 2));

			// update classification rate
			Instance currentInst = m_masterPlot.getPlotInstances().instance(
					index);
			double tp = currentInst.value(m_masterPlot.getPlotInstances()
					.attribute(ThresholdCurve.TRUE_POS_NAME).index());
			double tn = currentInst.value(m_masterPlot.getPlotInstances()
					.attribute(ThresholdCurve.TRUE_NEG_NAME).index());
			m_classificationAccV.setText(Utils.doubleToString((tp + tn)
					/ (totalPos + totalNeg) * 100.0, 4)
					+ "%");
		}

		private void updateInfoGivenIndex(int index) {
			Instances plotInstances = m_masterPlot.getPlotInstances();
			int indexOfSampleSize = m_masterPlot.getPlotInstances()
					.attribute(ThresholdCurve.SAMPLE_SIZE_NAME).index();
			int indexOfPercOfTarget = m_masterPlot.getPlotInstances()
					.attribute(ThresholdCurve.RECALL_NAME).index();
			int indexOfThreshold = m_masterPlot.getPlotInstances()
					.attribute(ThresholdCurve.THRESHOLD_NAME).index();

			// update labels
			m_percPopLab.setText(Utils.doubleToString(100.0 * plotInstances
					.instance(index).value(indexOfSampleSize), 4));
			m_percOfTargetLab.setText(Utils.doubleToString(
					100.0 * plotInstances.instance(index).value(
							indexOfPercOfTarget), 4));
			m_thresholdLab.setText(Utils.doubleToString(
					plotInstances.instance(index).value(indexOfThreshold), 4));
			/*
			 * if (m_percPop.isSelected()) {
			 * m_percPopLab.setText(Utils.doubleToString(100.0 * value, 4)); }
			 * else if (m_percOfTarget.isSelected()) {
			 * m_percOfTargetLab.setText(Utils.doubleToString(100.0 * value,
			 * 4)); } else { m_thresholdLab.setText(Utils.doubleToString(value,
			 * 4)); }
			 */

			// Update the highlighted point on the graphs */
			if (m_previousShapeIndex >= 0) {
				m_shapeSizes[m_previousShapeIndex] = 1;
			}

			m_shapeSizes[index] = 10;
			m_previousShapeIndex = index;

			// Update the confusion matrix
			// double totalInstances =
			int tp = plotInstances.attribute(ThresholdCurve.TRUE_POS_NAME)
					.index();
			int fp = plotInstances.attribute(ThresholdCurve.FALSE_POS_NAME)
					.index();
			int tn = plotInstances.attribute(ThresholdCurve.TRUE_NEG_NAME)
					.index();
			int fn = plotInstances.attribute(ThresholdCurve.FALSE_NEG_NAME)
					.index();
			Instance temp = plotInstances.instance(index);
			double totalInstances = temp.value(tp) + temp.value(fp)
					+ temp.value(tn) + temp.value(fn);
			// get the value out of the total pop field (if possible)
			double requestedPopSize = totalInstances;
			try {
				requestedPopSize = Double
						.parseDouble(m_totalPopField.getText());
			} catch (NumberFormatException e) {
			}

			m_conf_aa.setCellValue(temp.value(tp), totalInstances,
					requestedPopSize / totalInstances, 2);
			m_conf_ab.setCellValue(temp.value(fn), totalInstances,
					requestedPopSize / totalInstances, 2);
			m_conf_ba.setCellValue(temp.value(fp), totalInstances,
					requestedPopSize / totalInstances, 2);
			m_conf_bb.setCellValue(temp.value(tn), totalInstances,
					requestedPopSize / totalInstances, 2);

			updateCBRandomGainInfo(index);

			repaint();
		}

		private void updateInfoForSliderValue(double value) {
			int indexOfSampleSize = m_masterPlot.getPlotInstances()
					.attribute(ThresholdCurve.SAMPLE_SIZE_NAME).index();
			int indexOfPercOfTarget = m_masterPlot.getPlotInstances()
					.attribute(ThresholdCurve.RECALL_NAME).index();
			int indexOfThreshold = m_masterPlot.getPlotInstances()
					.attribute(ThresholdCurve.THRESHOLD_NAME).index();
			int indexOfMetric;

			if (m_percPop.isSelected()) {
				indexOfMetric = indexOfSampleSize;
			} else if (m_percOfTarget.isSelected()) {
				indexOfMetric = indexOfPercOfTarget;
			} else {
				indexOfMetric = indexOfThreshold;
			}

			Instances plotInstances = m_masterPlot.getPlotInstances();
			int index = findIndexForValue(value, plotInstances, indexOfMetric);
			updateInfoGivenIndex(index);
		}

		private int findIndexForValue(double value, Instances plotInstances,
				int indexOfMetric) {
			// binary search
			// threshold curve is sorted ascending in the threshold (thus
			// descending for recall and pop size)
			int index = -1;
			int lower = 0;
			int upper = plotInstances.numInstances() - 1;
			int mid = (upper - lower) / 2;
			boolean done = false;
			while (!done) {
				if (upper - lower <= 1) {

					// choose the one closest to the value
					double comp1 = plotInstances.instance(upper).value(
							indexOfMetric);
					double comp2 = plotInstances.instance(lower).value(
							indexOfMetric);
					if (Math.abs(comp1 - value) < Math.abs(comp2 - value)) {
						index = upper;
					} else {
						index = lower;
					}

					break;
				}
				double comparisonVal = plotInstances.instance(mid).value(
						indexOfMetric);
				if (value > comparisonVal) {
					if (m_threshold.isSelected()) {
						lower = mid;
						mid += (upper - lower) / 2;
					} else {
						upper = mid;
						mid -= (upper - lower) / 2;
					}
				} else if (value < comparisonVal) {
					if (m_threshold.isSelected()) {
						upper = mid;
						mid -= (upper - lower) / 2;
					} else {
						lower = mid;
						mid += (upper - lower) / 2;
					}
				} else {
					index = mid;
					done = true;
				}
			}

			// now check for ties in the appropriate direction
			if (!m_threshold.isSelected()) {
				while (index + 1 < plotInstances.numInstances()) {
					if (plotInstances.instance(index + 1).value(indexOfMetric) == plotInstances
							.instance(index).value(indexOfMetric)) {
						index++;
					} else {
						break;
					}
				}
			} else {
				while (index - 1 >= 0) {
					if (plotInstances.instance(index - 1).value(indexOfMetric) == plotInstances
							.instance(index).value(indexOfMetric)) {
						index--;
					} else {
						break;
					}
				}
			}
			return index;
		}

		/**
		 * Set the threshold data for the panel to use.
		 * 
		 * @param data
		 *            PlotData2D object encapsulating the threshold data.
		 * @param classAtt
		 *            the class attribute from the original data used to
		 *            generate the threshold data.
		 * @throws Exception
		 *             if something goes wrong.
		 */
		public synchronized void setDataSet(PlotData2D data, Attribute classAtt)
				throws Exception {
			// make a copy of the PlotData2D object
			m_masterPlot = new PlotData2D(data.getPlotInstances());
			boolean[] connectPoints = new boolean[m_masterPlot
					.getPlotInstances().numInstances()];
			for (int i = 1; i < connectPoints.length; i++) {
				connectPoints[i] = true;
			}
			m_masterPlot.setConnectPoints(connectPoints);

			m_masterPlot.m_alwaysDisplayPointsOfThisSize = 10;
			setClassForConfusionMatrix(classAtt);
			m_performancePanel.setMasterPlot(m_masterPlot);
			m_performancePanel.validate();
			m_performancePanel.repaint();

			m_shapeSizes = new int[m_masterPlot.getPlotInstances()
					.numInstances()];
			for (int i = 0; i < m_shapeSizes.length; i++) {
				m_shapeSizes[i] = 1;
			}
			m_masterPlot.setShapeSize(m_shapeSizes);
			constructCostBenefitData();
			m_costBenefitPanel.setMasterPlot(m_costBenefit);
			m_costBenefitPanel.validate();
			m_costBenefitPanel.repaint();

			m_totalPopPrevious = 0;
			m_fpPrevious = 0;
			m_tpPrevious = 0;
			m_tnPrevious = 0;
			m_fnPrevious = 0;
			m_previousShapeIndex = -1;

			// set the total population size
			Instance first = m_masterPlot.getPlotInstances().instance(0);
			double totalPos = first.value(m_masterPlot.getPlotInstances()
					.attribute(ThresholdCurve.TRUE_POS_NAME).index());
			double totalNeg = first.value(m_masterPlot.getPlotInstances()
					.attribute(ThresholdCurve.FALSE_POS_NAME));
			m_originalPopSize = (int) (totalPos + totalNeg);
			m_totalPopField.setText("" + m_originalPopSize);

			m_performancePanel.setYIndex(5);
			m_performancePanel.setXIndex(10);
			m_costBenefitPanel.setXIndex(0);
			m_costBenefitPanel.setYIndex(1);
			// System.err.println(m_masterPlot.getPlotInstances());
			updateInfoForSliderValue((double) m_thresholdSlider.getValue() / 100.0);
		}

		private void setClassForConfusionMatrix(Attribute classAtt) {
			m_classAttribute = classAtt;
			m_conf_actualA
					.setText(Messages
							.getInstance()
							.getString(
									"CostBenefitAnalysis_AnalysisPanel_SetClassForConfusionMatrix_Conf_ActualA_SetText_Text")
							+ classAtt.value(0));
			m_conf_actualA.setToolTipText(classAtt.value(0));
			String negClasses = "";
			for (int i = 1; i < classAtt.numValues(); i++) {
				negClasses += classAtt.value(i);
				if (i < classAtt.numValues() - 1) {
					negClasses += ",";
				}
			}
			m_conf_actualB
					.setText(Messages
							.getInstance()
							.getString(
									"CostBenefitAnalysis_AnalysisPanel_SetClassForConfusionMatrix_Conf_ActualB_SetText_Text")
							+ negClasses);
			m_conf_actualB.setToolTipText(negClasses);
		}

		private boolean constructCostBenefitData() {
			double tpCost = 0.0;
			try {
				tpCost = Double.parseDouble(m_cost_aa.getText());
			} catch (NumberFormatException n) {
			}
			double fpCost = 0.0;
			try {
				fpCost = Double.parseDouble(m_cost_ba.getText());
			} catch (NumberFormatException n) {
			}
			double tnCost = 0.0;
			try {
				tnCost = Double.parseDouble(m_cost_bb.getText());
			} catch (NumberFormatException n) {
			}
			double fnCost = 0.0;
			try {
				fnCost = Double.parseDouble(m_cost_ab.getText());
			} catch (NumberFormatException n) {
			}

			double requestedPopSize = m_originalPopSize;
			try {
				requestedPopSize = Double
						.parseDouble(m_totalPopField.getText());
			} catch (NumberFormatException e) {
			}

			double scaleFactor = 1.0;
			if (m_originalPopSize != 0) {
				scaleFactor = requestedPopSize / m_originalPopSize;
			}

			if (tpCost == m_tpPrevious && fpCost == m_fpPrevious
					&& tnCost == m_tnPrevious && fnCost == m_fnPrevious
					&& requestedPopSize == m_totalPopPrevious) {
				return false;
			}

			// First construct some Instances for the curve
			FastVector fv = new FastVector();
			fv.addElement(new Attribute("Sample Size"));
			fv.addElement(new Attribute("Cost/Benefit"));
			Instances costBenefitI = new Instances("Cost/Benefit Curve", fv,
					100);

			// process the performance data to make this curve
			Instances performanceI = m_masterPlot.getPlotInstances();

			for (int i = 0; i < performanceI.numInstances(); i++) {
				Instance current = performanceI.instance(i);

				double[] vals = new double[2];
				vals[0] = current.value(10); // sample size
				vals[1] = (current.value(0) * tpCost + current.value(1)
						* fnCost + current.value(2) * fpCost + current.value(3)
						* tnCost)
						* scaleFactor;
				Instance newInst = new Instance(1.0, vals);
				costBenefitI.add(newInst);
			}

			costBenefitI.compactify();

			// now set up the plot data
			m_costBenefit = new PlotData2D(costBenefitI);
			m_costBenefit.m_alwaysDisplayPointsOfThisSize = 10;
			m_costBenefit.setPlotName("Cost/benefit curve");
			boolean[] connectPoints = new boolean[costBenefitI.numInstances()];

			for (int i = 0; i < connectPoints.length; i++) {
				connectPoints[i] = true;
			}
			try {
				m_costBenefit.setConnectPoints(connectPoints);
				m_costBenefit.setShapeSize(m_shapeSizes);
			} catch (Exception ex) {
				// ignore
			}

			m_tpPrevious = tpCost;
			m_fpPrevious = fpCost;
			m_tnPrevious = tnCost;
			m_fnPrevious = fnCost;

			return true;
		}
	}

	/**
	 * Constructor.
	 */
	public CostBenefitAnalysis() {
		java.awt.GraphicsEnvironment ge = java.awt.GraphicsEnvironment
				.getLocalGraphicsEnvironment();
		if (!ge.isHeadless()) {
			appearanceFinal();
		}
	}

	/**
	 * Global info for this bean
	 * 
	 * @return a <code>String</code> value
	 */
	public String globalInfo() {
		return Messages.getInstance().getString(
				"CostBenefitAnalysis_GlobalInfo_Text");
	}

	/**
	 * Accept a threshold data event and set up the visualization.
	 * 
	 * @param e
	 *            a threshold data event
	 */
	public void acceptDataSet(ThresholdDataEvent e) {
		if (!GraphicsEnvironment.isHeadless()) {
			try {
				setCurveData(e.getDataSet(), e.getClassAttribute());
			} catch (Exception ex) {
				System.err.println(Messages.getInstance().getString(
						"CostBenefitAnalysis_AcceptDataSet_Error_Text"));
				ex.printStackTrace();
			}
		}
	}

	/**
	 * Set the threshold curve data to use.
	 * 
	 * @param curveData
	 *            a PlotData2D object set up with the curve data.
	 * @param origClassAtt
	 *            the class attribute from the original data used to generate
	 *            the curve.
	 * @throws Exception
	 *             if somthing goes wrong during the setup process.
	 */
	public void setCurveData(PlotData2D curveData, Attribute origClassAtt)
			throws Exception {
		if (m_analysisPanel == null) {
			m_analysisPanel = new AnalysisPanel();
		}
		m_analysisPanel.setDataSet(curveData, origClassAtt);
	}

	public BeanVisual getVisual() {
		return m_visual;
	}

	public void setVisual(BeanVisual newVisual) {
		m_visual = newVisual;
	}

	public void useDefaultVisual() {
		m_visual.loadIcons(BeanVisual.ICON_PATH + "DefaultDataVisualizer.gif",
				BeanVisual.ICON_PATH + "DefaultDataVisualizer_animated.gif");
	}

	public Enumeration enumerateRequests() {
		Vector newVector = new Vector(0);
		if (m_analysisPanel != null) {
			if (m_analysisPanel.m_masterPlot != null) {
				newVector.addElement("Show analysis");
			}
		}
		return newVector.elements();
	}

	public void performRequest(String request) {
		if (request.compareTo("Show analysis") == 0) {
			try {
				// popup visualize panel
				if (!m_framePoppedUp) {
					m_framePoppedUp = true;

					final javax.swing.JFrame jf = new javax.swing.JFrame(
							Messages.getInstance()
									.getString(
											"CostBenefitAnalysis_PerformRequest_Jf_JFrame_Text"));
					jf.setSize(1000, 600);
					jf.getContentPane().setLayout(new BorderLayout());
					jf.getContentPane().add(m_analysisPanel,
							BorderLayout.CENTER);
					jf.addWindowListener(new java.awt.event.WindowAdapter() {
						public void windowClosing(java.awt.event.WindowEvent e) {
							jf.dispose();
							m_framePoppedUp = false;
						}
					});
					jf.setVisible(true);
					m_popupFrame = jf;
				} else {
					m_popupFrame.toFront();
				}
			} catch (Exception ex) {
				ex.printStackTrace();
				m_framePoppedUp = false;
			}
		} else {
			throw new IllegalArgumentException(
					request
							+ Messages
									.getInstance()
									.getString(
											"CostBenefitAnalysis_PerformRequest_IllegalArgumentException_Text"));
		}
	}

	public void addVetoableChangeListener(String name,
			VetoableChangeListener vcl) {
		m_bcSupport.addVetoableChangeListener(name, vcl);
	}

	public BeanContext getBeanContext() {
		return m_beanContext;
	}

	public void removeVetoableChangeListener(String name,
			VetoableChangeListener vcl) {
		m_bcSupport.removeVetoableChangeListener(name, vcl);
	}

	protected void appearanceFinal() {
		removeAll();
		setLayout(new BorderLayout());
		setUpFinal();
	}

	protected void setUpFinal() {
		if (m_analysisPanel == null) {
			m_analysisPanel = new AnalysisPanel();
		}
		add(m_analysisPanel, BorderLayout.CENTER);
	}

	protected void appearanceDesign() {
		removeAll();
		m_visual = new BeanVisual("CostBenefitAnalysis", BeanVisual.ICON_PATH
				+ "ModelPerformanceChart.gif", BeanVisual.ICON_PATH
				+ "ModelPerformanceChart_animated.gif");
		setLayout(new BorderLayout());
		add(m_visual, BorderLayout.CENTER);
	}

	public void setBeanContext(BeanContext bc) throws PropertyVetoException {
		m_beanContext = bc;
		m_design = m_beanContext.isDesignTime();
		if (m_design) {
			appearanceDesign();
		} else {
			java.awt.GraphicsEnvironment ge = java.awt.GraphicsEnvironment
					.getLocalGraphicsEnvironment();
			if (!ge.isHeadless()) {
				appearanceFinal();
			}
		}
	}

	/**
	 * Returns true if, at this time, the object will accept a connection via
	 * the named event
	 * 
	 * @param eventName
	 *            the name of the event in question
	 * @return true if the object will accept a connection
	 */
	public boolean connectionAllowed(String eventName) {
		return (m_listenee == null);
	}

	/**
	 * Notify this object that it has been registered as a listener with a
	 * source for recieving events described by the named event This object is
	 * responsible for recording this fact.
	 * 
	 * @param eventName
	 *            the event
	 * @param source
	 *            the source with which this object has been registered as a
	 *            listener
	 */
	public void connectionNotification(String eventName, Object source) {
		if (connectionAllowed(eventName)) {
			m_listenee = source;
		}
	}

	/**
	 * Returns true if, at this time, the object will accept a connection
	 * according to the supplied EventSetDescriptor
	 * 
	 * @param esd
	 *            the EventSetDescriptor
	 * @return true if the object will accept a connection
	 */
	public boolean connectionAllowed(EventSetDescriptor esd) {
		return connectionAllowed(esd.getName());
	}

	/**
	 * Notify this object that it has been deregistered as a listener with a
	 * source for named event. This object is responsible for recording this
	 * fact.
	 * 
	 * @param eventName
	 *            the event
	 * @param source
	 *            the source with which this object has been registered as a
	 *            listener
	 */
	public void disconnectionNotification(String eventName, Object source) {
		if (m_listenee == source) {
			m_listenee = null;
		}

	}

	/**
	 * Get the custom (descriptive) name for this bean (if one has been set)
	 * 
	 * @return the custom name (or the default name)
	 */
	public String getCustomName() {
		return m_visual.getText();
	}

	/**
	 * Returns true if. at this time, the bean is busy with some (i.e. perhaps a
	 * worker thread is performing some calculation).
	 * 
	 * @return true if the bean is busy.
	 */
	public boolean isBusy() {
		return false;
	}

	/**
	 * Set a custom (descriptive) name for this bean
	 * 
	 * @param name
	 *            the name to use
	 */
	public void setCustomName(String name) {
		m_visual.setText(name);
	}

	/**
	 * Set a logger
	 * 
	 * @param logger
	 *            a <code>weka.gui.Logger</code> value
	 */
	public void setLog(Logger logger) {
		// we don't need to do any logging
	}

	/**
	 * Stop any processing that the bean might be doing.
	 */
	public void stop() {
		// nothing to do here
	}

	public static void main(String[] args) {
		try {
			Instances train = new Instances(new java.io.BufferedReader(
					new java.io.FileReader(args[0])));
			train.setClassIndex(train.numAttributes() - 1);
			weka.classifiers.evaluation.ThresholdCurve tc = new weka.classifiers.evaluation.ThresholdCurve();
			weka.classifiers.evaluation.EvaluationUtils eu = new weka.classifiers.evaluation.EvaluationUtils();
			// weka.classifiers.Classifier classifier = new
			// weka.classifiers.functions.Logistic();
			weka.classifiers.Classifier classifier = new weka.classifiers.bayes.NaiveBayes();
			FastVector predictions = new FastVector();
			eu.setSeed(1);
			predictions.appendElements(eu.getCVPredictions(classifier, train,
					10));
			Instances result = tc.getCurve(predictions, 0);
			PlotData2D pd = new PlotData2D(result);
			pd.m_alwaysDisplayPointsOfThisSize = 10;

			boolean[] connectPoints = new boolean[result.numInstances()];
			for (int i = 1; i < connectPoints.length; i++) {
				connectPoints[i] = true;
			}
			pd.setConnectPoints(connectPoints);
			final javax.swing.JFrame jf = new javax.swing.JFrame(
					Messages.getInstance()
							.getString(
									"CostBenefitAnalysis_PerformRequest_Main_JF_JFrame_Text"));
			jf.setSize(1000, 600);
			// jf.pack();
			jf.getContentPane().setLayout(new BorderLayout());
			final CostBenefitAnalysis.AnalysisPanel analysisPanel = new CostBenefitAnalysis.AnalysisPanel();

			jf.getContentPane().add(analysisPanel, BorderLayout.CENTER);
			jf.addWindowListener(new java.awt.event.WindowAdapter() {
				public void windowClosing(java.awt.event.WindowEvent e) {
					jf.dispose();
					System.exit(0);
				}
			});

			jf.setVisible(true);

			analysisPanel.setDataSet(pd, train.classAttribute());

		} catch (Exception ex) {
			ex.printStackTrace();
		}

	}
}